文章作者:Tyan
博客:noahsnail.com | CSDN | 简书
Item 7: Avoid finalizers
Finalizers are unpredictable, often dangerous, and generally unnecessary.Their use can cause erratic behavior, poor performance, and portability problems. Finalizers have a few valid uses, which we’ll cover later in this item, but as a rule of thumb, you should avoid finalizers.
终结方法通常是不可预测的,经常是危险的,一般来说是没必要的。使用它们会引起不稳定的行为,性能变低,可移植性问题等。终结方法有一些有效的使用,这个在本条目的后面会讲到,但根据经验,你应该避免使用终结方法。
C++ programmers are cautioned not to think of finalizers as Java’s analog of C++ destructors. In C++, destructors are the normal way to reclaim the resources associated with an object, a necessary counterpart to constructors. In Java, the garbage collector reclaims the storage associated with an object when it becomes unreachable, requiring no special effort on the part of the programmer. C++ destructors are also used to reclaim other nonmemory resources. In Java, the try-finally
block is generally used for this purpose.
C++程序员被警告说不要去想像Java中模拟C++析构函数那样的终结方法。在C++中,析构函数是一种正常回收对象资源的方式,是构造函数的必要对应。在Java中,当对象不可访问时,垃圾回收器会回收对象的相关资源,不需要程序员进行专门的工作。C++析构函数也用来回收其它的非内存资源。在Java中,try-finally
块用来完成这样的功能。
One shortcoming of finalizers is that there is no guarantee they’ll be executed promptly [JLS, 12.6]. It can take arbitrarily long between the time that an object becomes unreachable and the time that its finalizer is executed. This means that you should never do anything time-critical in a finalizer. For example, it is a grave error to depend on a finalizer to close files, because open file descriptors are a limited resource. If many files are left open because the JVM is tardy in executing finalizers, a program may fail because it can no longer open files.
终结方法的一个缺点是不能保证它们及时的执行[JLS,12.6]。从对象变得不可访问开始到它的终结方法被执行结束,这中间的时间可以任意长。这意味着你不应该在终结方法中做任何时间为关键的事情。例如,依赖终结方法来关闭文件是一个严重的错误,因为开放的文件描述符是一种有限的资源。如果许多文件都是打开状态,由于JVM执行终结方法时是迟缓的,因此程序可能失败,因为它不能再打开文件。
The promptness with which finalizers are executed is primarily a function of the garbage collection algorithm, which varies widely from JVM implementation to JVM implementation. The behavior of a program that depends on the promptness of finalizer execution may likewise vary. It is entirely possible that such a program will run perfectly on the JVM on which you test it and then fail miserably on the JVM favored by your most important customer.
尽快执行终结方法是垃圾回收算法的主要功能,在不同的JVM实现中变化很大。依赖终结方法执行及时性的程序同样变化很大。一个程序在测试它的JVM上运行非常完美,但在你最重要客户支持的JVM上它却糟糕地运行失败了,这是完全有可能的。
Tardy finalization is not just a theoretical problem. Providing a finalizer for a class can, under rare conditions, arbitrarily delay reclamation of its instances. A colleague debugged a long-running GUI application that was mysteriously dying with an OutOfMemoryError
. Analysis revealed that at the time of its death, the application had thousands of graphics objects on its finalizer queue just waiting to be finalized and reclaimed. Unfortunately, the finalizer thread was running at a lower priority than another application thread, so objects weren’t getting finalized at the rate they became eligible for finalization. The language specification makes no guarantees as to which thread will execute finalizers, so there is no portable way to prevent this sort of problem other than to refrain from using finalizers.
迟缓终结不仅仅是一个理论问题。在很少的情况下,为一个类提供终结方法可能会随意地延迟它实例的回收。有个同事调试一个长期运行的GUI应用,程序莫名其妙的死掉了,抛出了OutOfMemoryError
错误。分析表明在程序死亡时,应用中的终结方法队列中有成千上万的图形对象在等待被终结并回收。遗憾的是,终结方法线程的运行优先级要低于另一个应用线程,因此在另一个应用线程中的对象变得可以被终结时,它们不能被终结。语言规范不能保证哪一个线程来执行终结方法,因此没有轻便的方式来阻止这种问题的发生,除非避免使用终结方法。
Not only does the language specification provide no guarantee that finalizers will get executed promptly; it provides no guarantee that they’ll get executed at all. It is entirely possible, even likely, that a program terminates without executing finalizers on some objects that are no longer reachable. As a consequence, you should never depend on a finalizer to update critical persistent state. For example, depending on a finalizer to release a persistent lock on a shared resource such as a database is a good way to bring your entire distributed system to a grinding halt.
不仅语言规范不能保证终结方法及时的执行;而且也不能保证终结方法得到执行。这完全有可能,甚至有可能一个程序终止时,一些不能访问的对象的终结方法都没有执行。结论就是:你从不该依赖终结方法来更新重要的持续状态。例如,依赖一个终结方法来释放一个共享资源,例如数据库,的持续锁,很容易引起整个分布式系统突然当掉。
Don’t be seduced by the methods System.gc
and System.runFinalization
. They may increase the odds of finalizers getting executed, but they don’t guarantee it. The only methods that claim to guarantee finalization are System.runFinalizersOnExit
and its evil twin, Runtime.runFinalizersOnExit
. These methods are fatally flawed and have been deprecated [ThreadStop].
不要被System.gc
和System.runFinalization
方法诱惑。它们可能会增加终结方法得到执行的几率,但它们不能保证它。能保证终结方法执行的唯一方法是System.runFinalizersOnExit
以及它臭名昭著的孪生兄弟Runtime.runFinalizersOnExit
。这些方法都有致命的缺陷并且已经被废弃了[ThreadStop]。
In case you are not yet convinced that finalizers should be avoided, here’s another tidbit worth considering: if an uncaught exception is thrown during finalization, the exception is ignored, and finalization of that object terminates [JLS, 12.6]. Uncaught exceptions can leave objects in a corrupt state. If another thread attempts to use such a corrupted object, arbitrary nondeterministic behavior may result. Normally, an uncaught exception will terminate the thread and print a stack trace, but not if it occurs in a finalizer—it won’t even print a warning.
以防你还不相信终结方法应该被避免,这儿有另一个情况值得思考:如果在终结方法执行期间抛出了一个无法捕获的异常,这个异常被忽略了,对象的终结方法终止了[JLS,12.6]。不能捕获的异常可能会使对象处于崩溃状态。如果另一个线程试图使用这样一个崩溃的对象,任何不确定性的行为都有可能发送。通常,一个未被捕获的异常会终止线程并打印栈轨迹,但如果它发生在一个终结方法中,将不会打印出警告。
Oh, and one more thing: there is a severe performance penalty for using finalizers. On my machine, the time to create and destroy a simple object is about 5.6 ns. Adding a finalizer increases the time to 2,400 ns. In other words, it is about 430 times slower to create and destroy objects with finalizers.
哦,还有一件事:使用终结方法会有严重的性能问题。在我的机器上,创建并销毁一个简单对象大约是5.6纳秒。添加一个终结方法会将这个时间增加到2400纳秒。换句话说,创建一个对象并用终结方法销毁对象比正常情况下大约慢了430倍。
So what should you do instead of writing a finalizer for a class whose objects encapsulate resources that require termination, such as files or threads? Just provide an explicit termination method, and require clients of the class to invoke this method on each instance when it is no longer needed. One detail worth mentioning is that the instance must keep track of whether it has been terminated: the explicit termination method must record in a private field that the object is no longer valid, and other methods must check this field and throw an IllegalStateException
if they are called after the object has been terminated.
因此当一个类的对象封装的资源需要结束时,你应该用什么来代替一个类的终结方法?例如文件或线程?提供一个显式的结束方法,当类的实例不再需要时,要求类的客户端在每个实例上都调用这个方法。一个值得提及的细节是,实例必须跟踪它是否已经被终结:显式的结束方法必须记录在一个私有字段中,这个字段表明对象不再有效,如果其它方法再对象终结后调用对象,其它方法必须检查这个字段并抛出IllegalStateException
。
Typical examples of explicit termination methods are the close methods on InputStream
, OutputStream
, and java.sql.Connection
. Another example is the cancel
method on java.util.Timer
, which performs the necessary state change to cause the thread associated with a Timer
instance to terminate itself gently. Examples from java.awt
include Graphics.dispose
and Window.dispose
. These methods are often overlooked, with predictably dire performance consequences. A related method is Image.flush
, which deallocates all the resources associated with an Image
instance but leaves it in a state where it can still be used, reallocating the resources if necessary.
显式结束方法的典型例子是InputStream
,OutputStream
和java.sql.Connection
的关闭方法。另一个例子是java.util.Timer
的cancel
方法,它会进行必要的状态检查并一起线程相关的Timer
实例平稳的结束它自己。java.awt
的例子包括Graphics.dispose
和Window.dispose
。这些方法经常被忽视,可以预料会引起可怕的性能后果。一个相关的方法是Image.flush
,它会释放所有Image
实例相关的资源,但会将实例保持在一个可用的状态,如果必要的时候重新分配资源。
Explicit termination methods are typically used in combination with the try-finally
construct to ensure termination. Invoking the explicit termination method inside the finally
clause ensures that it will get executed even if an exception is thrown while the object is being used:
显式结束方法通过与try-finally
结构结合来确保终结。在finally
语句块的内部调用显式的结束方法来确保它得到执行,即使对象使用时抛出了一个异常:
1 | // try-finally block guarantees execution of termination method |
So what, if anything, are finalizers good for? There are perhaps two legitimate uses. One is to act as a “safety net” in case the owner of an object forgets to call its explicit termination method. While there’s no guarantee that the finalizer will be invoked promptly, it may be better to free the resource late than never, in those (hopefully rare) cases when the client fails to call the explicit termination method. But the finalizer should log a warning if it finds that the resource has not been terminated, as this indicates a bug in the client code, which should be fixed. If you are considering writing such a safety-net finalizer, think long and hard about whether the extra protection is worth the extra cost.
那终结方法有什么好处呢?有两种可能的合法应用。一个是作为『安全网』,以防对象拥有者忘记调用它的显式结束方法。但这不能保证终结方法得到及时的调用,当客户端调用显式结束方法失败时,在那种情况下(希望很少),后面释放资源总比不释放资源要好。但终结方法如果发现资源仍没有被释放,它应该输出一个警告,因为这意味着客户端代码存在一个BUG,它应该被修正。如果你正在考虑写这样一个安全网终结方法,要仔细思考这种额外的保护是否值得额外的代价。
The four classes cited as examples of the explicit termination method pattern (FileInputStream
, FileOutputStream
, Timer
, and Connection
) have finalizers that serve as safety nets in case their termination methods aren’t called. Unfortunately these finalizers do not log warnings. Such warnings generally can’t be added after an API is published, as it would appear to break existing clients.
作为显式结束方法模式引用的四个例子(FileInputStream
,FileOutputStream
,Timer
和Connection
)都有终结方法作为安全网以防它们的结束方法没有被调用。遗憾的是这些终结方法不输出警告。这种警告通常在API发布后不能进行添加,因为它会损坏现有的客户端。
A second legitimate use of finalizers concerns objects with native peers. A native peer is a native object to which a normal object delegates via native methods. Because a native peer is not a normal object, the garbage collector doesn’t know about it and can’t reclaim it when its Java peer is reclaimed. A finalizer is an appropriate vehicle for performing this task, assuming the native peer holds no critical resources. If the native peer holds resources that must be terminated promptly, the class should have an explicit termination method, as described above. The termination method should do whatever is required to free the critical resource. The termination method can be a native method, or it can invoke one.
终结方法的第二个合法使用是关于对象的本地对等体。本地对等体是一个本地对象,普通对象通过本地方法委托给本地对象。由于本地对等体不是一个正常的对象,当它的Java对等体回收时,垃圾回收器不知道并且不能回收它。假设本地对等体不拥有重要的资源,终结方法是执行这个任务的合适工具。如果本地对等体拥有必须及时终止的资源,这个类应该有一个显式的结束方法,如上所述。结束方法应该用来释放重要资源。结束方法可以是一个本地方法或它可以调用一个本地方法。
It is important to note that “finalizer chaining” is not performed automatically. If a class (other than Object
) has a finalizer and a subclass overrides it, the subclass finalizer must invoke the superclass finalizer manually. You should finalize the subclass in a try
block and invoke the superclass finalizer in the corresponding finally
block. This ensures that the superclass finalizer gets executed even if the subclass finalization throws an exception and vice versa. Here’s how it looks. Note that this example uses the Override
annotation (@Override
), which was added to the platform in release 1.5. You can ignore Override
annotations for now, or see Item 36 to find out what they mean:
很重要的一点就是要注意『终结方法链』是不能自动执行的。如果一个类(不是Object
)有一个终结方法,一个子类覆写了它,子类终结方法必须手动调用父类终结方法。你应该try
块内终止这个子类并在对应的finally
块调用父类终结方法。这保证了父类终结方法得到了执行,即使子类终结方法抛出异常,反之亦然。下面是它的一个例子、注意这个例子使用了Override
注解(@Override
),在release 1.5版本中添加。现在你可以忽略Override
注解,或看Item 36弄明白它是什么意思:
1 |
|
If a subclass implementor overrides a superclass finalizer but forgets to invoke it, the superclass finalizer will never be invoked. It is possible to defend against such a careless or malicious subclass at the cost of creating an additional object for every object to be finalized. Instead of putting the finalizer on the class requiring finalization, put the finalizer on an anonymous class (Item 22) whose sole purpose is to finalize its enclosing instance. A single instance of the anonymous class, called a finalizer guardian, is created for each instance of the enclosing class. The enclosing instance stores the sole reference to its finalizer guardian in a private instance field so the finalizer guardian becomes eligible for finalization at the same time as the enclosing instance. When the guardian is finalized, it performs the finalization activity desired for the enclosing instance, just as if its finalizer were a method on the enclosing class:
如果一个子类实现者覆写了父类的终结方法但忘了调用它,父类终结方法将会从未调用。要注意防范这种粗心的或邪恶的子类是有可能的,代价就是为每个要被终结的对象创建一个额外的对象。代替将终结方法放在需要终结的类中,将终结方法放在一个匿名类中(Item 22),它的唯一目的就是终结它外围实例。匿名类的单个实例,被称为终结方法守护者,为外围类的每个实例创建一个匿名类实例。外围实例在一个私有字段存储了它的终结方法守护者的唯一引用,因此终结方法守护者与外围实例可以同时进行终结。当守护者被终结时,它会执行外围实例要求的终结活动,就像它的终结方法是外围实例的一个方法一样:
1 | // Finalizer Guardian idiom |
Note that the public class, Foo
, has no finalizer (other than the trivial one it inherits from Object
), so it doesn’t matter whether a subclass finalizer calls super.finalize
or not. This technique should be considered for every nonfinal public class that has a finalizer.
注意公有类Foo
没有终结方法(除非它从Object
继承一个无关紧要的),因此子类的终结方法是否调用super.finalize
是不重要的。每一个含有终结方法的非终结公有类都应该考虑这个技术。
In summary, don’t use finalizers except as a safety net or to terminate noncritical native resources. In those rare instances where you do use a finalizer, remember to invoke super.finalize
. If you use a finalizer as a safety net, remember to log the invalid usage from the finalizer. Lastly, if you need to associate a finalizer with a public, nonfinal class, consider using a finalizer guardian, so finalization can take place even if a subclass finalizer fails to invoke super.finalize
.
总结:不要使用终结方法,除非是用作安全网或用来终止一个非重要的本地资源。在那些你使用终结方法的稀少实例中,记住调用super.finalize
。如果你使用终结方法作为安全网,记住在终结方法中输出非法用法。最后,如果你需要将终结方法关联到一个公有的,非终结类,考虑使用终结方法守护者,即使子类终结方法调用super.finalize
失败,也会进行终结。